 --[[-------------------------------------------------------------------------
  Copyright (c) 2006, Jim Whitehead II <cladhaire@gmail.com>
  All rights reserved.

  Redistribution and use in source and binary forms, with or without
  modification, are permitted provided that the following conditions are
  met:

      * Redistributions of source code must retain the above copyright
        notice, this list of conditions and the following disclaimer.
      * Redistributions in binary form must reproduce the above
        copyright notice, this list of conditions and the following
        disclaimer in the documentation and/or other materials provided
        with the distribution.
      * Neither the name of PerfectRaid nor the names of its contributors 
        may be used to endorse or promote products derived from this software
        without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHsqsANTABILITY AND FITNESS FOR
  A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------------------------]]

local frames = {}
local unavail = {}

local L
local utils = DongleStub("DongleUtils")
PerfectRaid = DongleStub("Dongle"):New("PerfectRaid")
PerfectRaid.frames = frames

function PerfectRaid:Initialize()
	self.defaults = {
		profile = {
			headers = {},
			buffs = {},
			positions = {},
			simple = {
				voffset = 15,
				titles = true,
			},
			mode = "double",
		},
	}

	self.db = self:InitializeDB("PerfectRaidDB", self.defaults)
	L = self.LOCALS

	if not ClickCastFrames then
		ClickCastFrames = {}
	end
end

function PerfectRaid:Enable()
	self:RegisterEvent("RAID_ROSTER_UPDATE")
	self:RegisterEvent("UNIT_HEALTH")
	self:RegisterEvent("UNIT_MAXHEALTH")
	self:RegisterEvent("UNIT_MANA", "UNIT_MANA")
	self:RegisterEvent("UNIT_RAGE", "UNIT_MANA")
	self:RegisterEvent("UNIT_ENERGY", "UNIT_MANA")
	self:RegisterEvent("UNIT_FOCUS", "UNIT_MANA")
	self:RegisterEvent("UNIT_MAXMANA")
	self:RegisterEvent("UNIT_MAXRAGE", "UNIT_MAXMANA")
	self:RegisterEvent("UNIT_MAXENERGY", "UNIT_MAXMANA")
	self:RegisterEvent("UNIT_MAXFOCUS", "UNIT_MAXMANA")
	self:RegisterEvent("UNIT_AURA")

	local mode = self.db.profile.mode
	if mode == "singleclass" then
		self:CreateSingleColumn()
	elseif mode == "singlegroup" then
		self:CreateSingleColumn("group")
	elseif mode == "double" then
		self:CreateDoubleColumn()
	elseif mode == "advanced" then
		self:CreateRaidHeaders()
	end
end

function PerfectRaid:CreateSingleColumn(sort)
	self.column = self.column or {}
	local column = self.column

	local classes = {"Warrior","Priest","Druid","Shaman","Paladin","Rogue","Mage","Warlock","Hunter"}
	local voffset = self.db.profile.simple.voffset * -1
	local titles = self.db.profile.simple.titles

	if sort == "group" then
		classes = {"Group 1","Group 2","Group 3","Group 4","Group 5","Group 6","Group 7","Group8"}
	end

	for i=1,#classes do
		local filter
		if sort == "group" then
			filter = string.sub(classes[i], -1, -1) 
		else
			filter = string.upper(classes[i])
		end
		local title = titles and classes[i] or nil

		if not column[i] then
			column[i] = self:CreateRaidFrame("PRColumn"..i, title, filter, nil, column[1])
			column[i]:Show()
		else
			self:ChangeRaidFrame("PRColumn"..i, title, filter, nil, column[1])
			column[i]:Show()
			column[i]:ClearAllPoints()
		end
	end

	self:RestorePosition("PRColumn1")

	for i=2,#classes do
		self.column[i].locked = true
		self.column[i]:SetPoint("TOP", self.column[i-1], "BOTTOM", 0, voffset)
	end

	if GetNumRaidMembers() <= 0 then
		for i,frame in ipairs(column) do
			frame:Hide()
		end
	end
end

function PerfectRaid:CreateDoubleColumn()
	self.column = self.column or {}
	local column = self.column

	local voffset = self.db.profile.simple.voffset * -1
	local titles = self.db.profile.simple.titles
	local classes = {"Group 1","Group 2","Group 3","Group 4","Group 5","Group 6","Group 7","Group8"}

	for i=1,#classes do
		local filter = string.sub(classes[i], -1, -1) 
		local title = titles and classes[i] or nil

		if not column[i] then
			column[i] = self:CreateRaidFrame("PRColumn"..i, title, filter, nil, column[1])
			column[i]:Show()
		else
			self:ChangeRaidFrame("PRColumn"..i, title, filter, nil, column[1])
			column[i]:Show()
			column[i]:ClearAllPoints()
		end
	end

	self:RestorePosition("PRColumn1")

	for i=2,4 do
		column[i].locked = true
		column[i]:SetPoint("TOP", column[i-1], "BOTTOM", 0, voffset)
	end
	column[5]:SetPoint("TOPLEFT", column[1], "TOPRIGHT", 50, 0)
	for i=6,8 do
		column[i].locked = true
		column[i]:SetPoint("TOP", column[i-1], "BOTTOM", 0, voffset)
	end
	if GetNumRaidMembers() <= 0 then
		for i,frame in ipairs(column) do
			frame:Hide()
		end
	end
end

function PerfectRaid:CreateRaidHeaders()
	local list = self.db.profile.headers
	self.headers = self.headers or {}

	local inraid = GetNumRaidMembers() > 0

	for i=1,#list do
		local entry = list[i]
		local name = "PRHeader"..i
		local filters = strjoin(",", unpack(entry.filters))

		if self.headers[i] then
			self:ChangeRaidFrame(name, entry.name, filters, entry.strict)
		else
			self.headers[i] = self:CreateRaidFrame(name, entry.name, filters, entry.strict)
			self:RestorePosition("PRHeader"..i)
		end

		if not inraid then
			self.headers[i]:Hide()
		else
			self.headers[i]:Show()
		end
	end
end

function PerfectRaid:SavePosition(name)
    local f = getglobal(name)
    local x,y = f:GetLeft(), f:GetTop()
    local s = f:GetEffectiveScale()
    
    x,y = x*s,y*s
    
	local opt = self.db.profile.positions[name]
	if not opt then 
		self.db.profile.positions[name] = {}
		opt = self.db.profile.positions[name]
	end
    opt.PosX = x
    opt.PosY = y
end

function PerfectRaid:RestorePosition(name)
	local f = getglobal(name)
	local opt = self.db.profile.positions[name]
	if not opt then 
		self.db.profile.positions[name] = {}
		opt = self.db.profile.positions[name]
	end

	local x = opt.PosX
	local y = opt.PosY

    local s = f:GetEffectiveScale()
        
    if not x or not y then
        f:ClearAllPoints()
        f:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
        return 
    end

    x,y = x/s,y/s

    f:ClearAllPoints()
	f:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", x, y)
end

function PerfectRaid:ChangeRaidFrame(name, title, filter, strict, dragparent)
	local frame = getglobal(name)
	frame.title:SetText(title or "")
	frame:SetAttribute("groupFilter", filter)
	frame:SetAttribute("strictFiltering", strict)
	frame:SetAttribute("dragparent", dragparent or frame)
	frame:Hide()
	frame:Show()
end

local function OnDragStart(frame)
	local parent = frame:GetAttribute("dragparent")
	if not parent.locked then
		parent:StartMoving()
		PerfectRaid.moving = parent
	end
end

local function OnDragStop(frame)
	local parent = frame:GetAttribute("dragparent")
	parent:StopMovingOrSizing()
	PerfectRaid:SavePosition(parent:GetName())
	PerfectRaid.moving = nil
end

function PerfectRaid:CreateRaidFrame(name, title, filter, strict, dragparent)
	local frame = CreateFrame("Frame", name, UIParent, "SecureRaidGroupHeaderTemplate")
	frame.title = frame:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
	frame.title:SetPoint("BOTTOM", frame, "TOP", 0, 3)
	frame.title:SetText(title or "")
	frame:SetAttribute("point", "TOP")
	frame:SetAttribute("groupFilter", filter)
	frame:SetAttribute("template", "SecureUnitButtonTemplate")
	frame:SetAttribute("templateType", "Button")
	frame:SetAttribute("yOffset", -2)
	frame:SetAttribute("sortMethod", "NAME")
	frame:SetAttribute("strictFiltering", strict)
	frame:SetAttribute("dragparent", dragparent or frame)
	frame.initialConfigFunction = PerfectRaid.ConfigureButton
	frame:SetMovable(true)
	frame:SetClampedToScreen(true)
	frame:Show()
	return frame
end

function PerfectRaid:CreatePartyFrame()
	-- Create the player frame first, in case we need it

	local frame = CreateFrame("Frame", "PRaidPartyHeader", UIParent, "SecurePartyHeaderTemplate")
	frame:SetAttribute("point", "TOP")
	frame:SetAttribute("template", "SecureUnitButtonTemplate")
	frame:SetAttribute("templateType", "Button")
	frame:SetAttribute("nameList", UnitName("player"))
	frame:SetAttribute("yOffset", -2)

	frame.initialConfigFunction = PerfectRaid.ConfigureButton
	frame:SetPoint("CENTER", 0, 0)
	frame:SetMovable(true)
	frame:SetClampedToScreen(true)

	local frame = CreateFrame("Button", "PRaidPlayerFrame", PRaidPartyHeader, "SecureUnitButtonTemplate")
	PerfectRaid.ConfigureButton(frame)
	frame:SetAttribute("unit", "player")
	frame:SetPoint("BOTTOM", PRaidPartyHeader, "TOP", 0, 2)
	frame:SetScript("OnUpdate", PerfectRaid.OnUpdate)

	PRaidPartyHeader:Show()
end

function PerfectRaid:GetColoredName(unit)
	local class = select(2, UnitClass(unit)) or "WARRIOR"
	local color = RAID_CLASS_COLORS[class]
	return string.format("|cFF%02x%02x%02x%s|r", color.r*255, color.g*255, color.b*255, UnitName(unit) or "Unknown")
end

function PerfectRaid:UNIT_HEALTH(event, unit)
	if not frames[unit] then return end
	local health = UnitHealth(unit)
	local max = UnitHealthMax(unit)
	-- Hack to fix api issue
	if max < health then max = health end
	local deficit = max - health
	local perc = UnitHealthMax(unit) / health
	local class = select(2, UnitClass(unit))
	local perc = (health/max) or 0

	if deficit > 999 then 
		deficit = string.format("%.1fk", deficit / 1000)
	elseif deficit == 0 then
		deficit = ""
	end

	if UnitIsDead(unit) or UnitIsGhost(unit) or not UnitIsConnected(unit) then
		local status

		unavail[unit] = true

		if UnitIsDead(unit) then 
			status = L.STATUS_DEAD
		elseif UnitIsGhost(unit) then
			status = L.STATUS_GHOST
		elseif not UnitIsConnected(unit) then
			status = L.STATUS_OFFLINE
		end			

		for frame in pairs(frames[unit]) do
			frame.healthbar:SetValue(max)
			frame.manabar:SetValue(max)
			frame.status:SetText(status)
			frame.healthbar:SetStatusBarColor(0.3, 0.3, 0.3)
			frame.manabar:SetStatusBarColor(0.3, 0.3, 0.3)
			frame:SetBackdropBorderColor(0.3, 0.3, 0.3)
		end
	else
		if unavail[unit] then
			for frame in pairs(frames[unit]) do
				local color = frame.classcolor
--				frame.healthbar:SetStatusBarColor(color.r, color.g, color.b)
				frame.healthbar:SetStatusBarColor(utils.GetHPSeverity(perc))
				frame.healthbar:SetMinMaxValues(0, UnitHealthMax(unit))
				frame.healthbar:SetValue(UnitHealth(unit))
				frame:SetBackdropBorderColor(color.r, color.g, color.b)

				local color = frame.manacolor
				frame.manabar:SetStatusBarColor(color.r, color.g, color.b)
				frame.manabar:SetMinMaxValues(0, UnitManaMax(unit))
				frame.manabar:SetValue(UnitMana(unit))

				frame.status:SetText(nil)
			end
			unavail[unit] = nil
		end

		for frame in pairs(frames[unit]) do
			if not range then frame:SetAlpha(0.3) else frame:SetAlpha(1.0) end
			local class = select(2, UnitClass(unit))
			frame.healthbar:SetValue(health)
			frame.healthbar:SetStatusBarColor(utils.GetHPSeverity(perc))
			frame.status:SetText(deficit)
		end
	end
end

function PerfectRaid:UNIT_MANA(event, unit)
	if not frames[unit] then return end
	local mana = UnitMana(unit)
	
	for frame in pairs(frames[unit]) do
		frame.manabar:SetValue(mana)
	end
end

function PerfectRaid:UNIT_MAXHEALTH(event, unit)
	if not frames[unit] then return end
	local max = UnitHealthMax(unit)
	local health = UnitHealth(unit)
	for frame in pairs(frames[unit]) do
		frame.healthbar:SetMinMaxValues(0, max)
		frame.healthbar:SetValue(health)
	end
end

function PerfectRaid:UNIT_MAXMANA(event, unit)
	if not frames[unit] then return end
	local max = UnitManaMax(unit)
	local mana = UnitMana(unit)
	for frame in pairs(frames[unit]) do
		frame.manabar:SetMinMaxValues(0, max)
		frame.manabar:SetValue(mana)
	end
end


local function OnShow(frame)
	local self = PerfectRaid
	local unit = frame:GetAttribute("unit")
	frame.name:SetText(self:GetColoredName(unit))
--	frame.name:SetText(UnitName(unit))

	local class = select(2, UnitClass(unit)) or "WARRIOR"
	frame.class = class 
	frame.classcolor = RAID_CLASS_COLORS[class]
	frame.manacolor = ManaBarColor[UnitPowerType(unit)] 

	local color = frame.classcolor
	frame.healthbar:SetStatusBarColor(color.r, color.g, color.b)	
	frame.healthbar:SetMinMaxValues(0, UnitHealthMax(unit))
	frame.healthbar:SetValue(UnitHealth(unit))
	frame:SetBackdropBorderColor(color.r, color.g, color.b)

    local color = frame.manacolor
    frame.manabar:SetStatusBarColor(color.r, color.g, color.b)
	frame.manabar:SetMinMaxValues(0, UnitManaMax(unit))
	frame.manabar:SetValue(UnitMana(unit))

	frame.status:SetText(nil)

	frames[unit] = frames[unit] or {}
	frames[unit][frame] = true

	self:UNIT_HEALTH(nil, unit)
	self:UNIT_MANA(nil, unit)
	self:UNIT_AURA(nil, unit)
end

local function OnHide(frame)
	local unit = frame:GetAttribute("unit")
	frames[unit][frame] = nil
end

local function OnAttributeChanged(frame, name, value)
	if name ~= "unit" then return end

	for unit,tbl in pairs(frames) do
		if tbl[frame] and frame:GetAttribute("unit") ~= unit then
			tbl[frame] = nil
		end
	end
end

function PerfectRaid.ConfigureButton(button)
	button:SetWidth(160)
	button:SetHeight(14)
--	button:SetBackdrop(GameTooltip:GetBackdrop())
	button:SetBackdropColor(0.3, 0.3, 0.3)
	button:SetBackdropBorderColor(0.4, 0.4, 0.4)

	ClickCastFrames[button] = true
	button:SetAttribute("*type1", "target")

	local parent = button:GetParent()
	button:SetAttribute("dragparent", parent:GetAttribute("dragparent"))

	local bar = CreateFrame("StatusBar", nil, button)
	bar:SetPoint("TOPLEFT", 70, 0)
	bar:SetPoint("BOTTOMRIGHT", 0, 0)
	bar:SetStatusBarTexture("Interface\\AddOns\\PerfectRaid\\images\\smooth")
	button.healthbar = bar

	local bar = CreateFrame("StatusBar", nil, button.healthbar)
	bar:SetPoint("BOTTOMLEFT", 0, 0)
	bar:SetPoint("BOTTOMRIGHT", 0, 0)
	bar:SetHeight(2)
	bar:SetStatusBarTexture("Interface\\AddOns\\PerfectRaid\\images\\smooth")
	button.manabar = bar
	bar:Hide()

	local font = button.healthbar:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
	font:SetPoint("RIGHT", button.healthbar, "LEFT", -2, 0)
	font:SetPoint("LEFT", button, "LEFT", 0, 0)
	font:SetJustifyH("RIGHT")
	button.name = font	

	local font = button.healthbar:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
	font:SetPoint("RIGHT", -2, 2)
	button.status = font

	local font = button.healthbar:CreateFontString(nil, "ARTWORK", "GameFontHighlightSmall")
	font:SetPoint("LEFT", button.healthbar, "RIGHT", 3, 0)
	button.aura = font

	button:SetClampedToScreen(true)
	button:RegisterForDrag("LeftButton")

	button:SetScript("OnShow", OnShow)
	button:SetScript("OnDragStart", OnDragStart)
	button:SetScript("OnDragStop", OnDragStop)
	button:SetScript("OnAttributeChanged", OnAttributeChanged)
end

function PerfectRaid:RAID_ROSTER_UPDATE()
	if GetNumRaidMembers() > 0 then
		--PRaidPartyHeader:Hide()
		if self.moving then
			self.moving:StopMovingOrSizing()
		end
		self:ShowHideTitles()					
	else
		if PRaidPartyHeader then
			PRaidPartyHeader:Show()
		end
		for unit,tbl in pairs(frames) do
			for k,v in pairs(tbl) do
				tbl[k] = nil
			end
		end
		if self.headers then
			for k,v in pairs(self.headers) do
				v.title:Hide()
			end
		end
		if self.column then
			for k,v in pairs(self.column) do
				v.title:Hide()
			end
		end
	end	
end

function PerfectRaid:ShowHideTitles()
	if self.column then
		for i,frame in pairs(self.column) do
			local active
			for i=1,frame:GetNumChildren() do
				local button = frame:GetAttribute("Child"..i)
				if button:IsShown() then
					active = true
				end
			end
			if not active then
				frame.title:Hide()
			else
				frame.title:Show()
			end
		end
	end

	if self.headers then
		for i,frame in pairs(self.column) do
			local active
			for i=1,frame:GetNumChildren() do
				local button = frame:GetAttribute("Child"..i)
				if button:IsShown() then
					active = true
				end
			end
			if not active then
				frame.title:Hide()
			else
				frame.title:Show()
			end
		end
	end
end

local class = select(2, UnitClass("player"))
local spells = {
	DRUID = "Healing Touch",
	SHAMAN = "Healing Wave",
	PRIEST = "Lesser Heal",
	PALADIN = "Holy Light",
}

local rangespell = spells[class]

local elapsed = 0
function PerfectRaid.OnUpdate(frame, arg1)
	for unit,tbl in pairs(frames) do
		local range = IsSpellInRange(rangespell, unit) == 1
		local alpha = range and 1.0 or 0.3
		for frame in pairs(tbl) do
			frame:SetAlpha(alpha)
		end
	end
	elapsed = 0
end

local frame = CreateFrame("Frame", "PRUpdateFrame")
frame:SetScript("OnUpdate", PerfectRaid.OnUpdate)

local buffcache = {}
local work = {}
function PerfectRaid:UNIT_AURA(event, unit)
	if not frames[unit] then return end
	for k,v in pairs(buffcache) do buffcache[k] = nil end

	for i=1,40 do
		local name,rank,texture,count = UnitBuff(unit, i)
		if not name then break end
		buffcache[name] = (buffcache[name] or 0) + 1
	end	

	for i=1,40 do
		local name,rank,texture,count = UnitDebuff(unit, i)
		if not name then break end
		buffcache[name] = (buffcache[name] or 0) + 1
	end

	for k,v in pairs(work) do work[k] = nil end

	local buffs = self.db.profile.buffs
	for i,entry in ipairs(buffs) do
		local checkcond = false
		if entry.missing then
			if not buffcache[entry.buffname] and not buffcache[entry.groupbuff or "nil"] then
				checkcond = true
			end
		else
			if buffcache[entry.buffname] or buffcache[entry.groupbuff or "nil"] then
				checkcond = true
			end
		end
		
		local conds = self.conditions
		if checkcond then
			for i,name in pairs(entry.cond) do
				if conds[name] and conds[name](unit) then
					table.insert(work, entry.colortext)
				end
			end
		end
	end

	local status = strjoin(" ", unpack(work))
	for frame in pairs(frames[unit]) do
		frame.aura:SetText(status)
	end			
end
	
--seterrorhandler(function(msg) PerfectRaid:Print(msg .. debugstack()) end)

SLASH_PERFECTRAID1 = "/praid"
SLASH_PERFECTRAID2 = "/perfectraid"

SlashCmdList["PERFECTRAID"] = function(msg)
	-- Slash Command Handler
	if InCombatLockdown() then
		PerfectRaid:Print("You cannot change option when in combat")
		return
	end

	if not PROptions then
		PerfectRaid:CreateOptions()
		PROptions:Show()
	else
		PROptions:Show()
	end
end

